package com.xiaoxin.experience.service.ssh;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xiaoxin.experience.common.RestResponse;
import com.xiaoxin.experience.service.helper.RobotHelper;
import com.xiaoxin.experience.service.ssh.entity.SSHEntity;
import com.xiaoxin.experience.service.ssh.mapper.SSHMapper;
import com.xiaoxin.experience.service.ssh.model.FileInfo;
import com.xiaoxin.experience.service.ssh.model.LastQueryInfo;
import com.xiaoxin.experience.service.ssh.model.RemoteFileInfo;
import com.xiaoxin.experience.service.ssh.model.SSHSessionInfo;
import com.xiaoxin.experience.system.properties.PropertiesHelper;
import com.xiaoxin.experience.util.CollectionUtil;
import com.xiaoxin.experience.util.FileUtil;
import com.xiaoxin.experience.util.StringUtil;
import com.xiaoxin.experience.util.ThreadUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.swing.*;
import javax.swing.filechooser.FileSystemView;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;

/**
 * @author xiaoxin
 */
@Slf4j
@Service
public class SSHService extends ServiceImpl<SSHMapper, SSHEntity>
{
    @Resource
    private RobotHelper robotHelper;

    private LastQueryInfo lastQueryInfo = LastQueryInfo.defaultInfo();

    public List<SSHSessionInfo> getSSHSessionInfoList()
    {
        List<SSHEntity> sshEntities = baseMapper.selectList(null);
        return sshEntities.stream().map(SSHEntity::toInfo).collect(Collectors.toList());
    }

    @Transactional(rollbackFor = Exception.class)
    public boolean saveOrUpdate(SSHSessionInfo sshSessionInfo)
    {
        if (Objects.isNull(sshSessionInfo))
        {
            return false;
        }
        return saveOrUpdate(new SSHEntity(sshSessionInfo));
    }

    public void removeSession(String ip)
    {
        baseMapper.deleteById(ip);
    }

    public void releaseClient(String ip)
    {
        SSHJClientManager.removeSSHJClient(ip);
    }

    public RestResponse<Void> download(SSHSessionInfo sshSessionInfo,String remoteFilePath,String localDownloadDir)
    {
        try
        {
            File downDir = new File(localDownloadDir);
            if (!downDir.exists())
            {
                boolean mkdirs = downDir.mkdirs();
                if (!mkdirs)
                {
                    return RestResponse.fail("创建下载目录失败");
                }
            }
            SSHJClient sshjClient = SSHJClientManager.getSSHJClient(sshSessionInfo);
            sshjClient.getFile(remoteFilePath,localDownloadDir);
        }
        catch (Exception e)
        {
            return RestResponse.fail("下载失败");
        }
        return RestResponse.success();
    }

    private List<String> buildShellCmd(List<String> fileNames,String restartServerCommand)

    {
        List<String> cmds = new ArrayList<>();
        if (CollectionUtil.isEmpty(fileNames) || StringUtil.isBlank(restartServerCommand))
        {
            return cmds;
        }
        for (String fileName : fileNames)
        {
            String replaced = restartServerCommand.replaceAll("\\{fileName}", removeExtensions(fileName));
            String[] split = replaced.split(";");
            cmds.addAll(Arrays.asList(split));
        }
        return cmds;
    }

    private String removeExtensions(String fileName)
    {
        if (StringUtil.isBlank(fileName))
        {
            return fileName;
        }
        int index = fileName.lastIndexOf(".");
        if (index == -1)
        {
            return fileName;
        }
        return fileName.substring(0, index);
    }

    public RestResponse<List<RestResponse<Void>>> batchUploadRestart(List<SSHSessionInfo> sshSessionInfos, List<String[]> remoteDirAndLocalFilePaths, boolean restart, List<String> fileNames)
    {
        List<CompletableFuture<RestResponse<Void>>> futures = new ArrayList<>();
        for (SSHSessionInfo sshSessionInfo : sshSessionInfos)
        {
            CompletableFuture<RestResponse<Void>> fileCompletableFuture = CompletableFuture.supplyAsync(
                    ()->batchUploadRestart(sshSessionInfo,remoteDirAndLocalFilePaths,restart,fileNames),ThreadUtil.defaultThreadPool()).exceptionally(e ->
                    null);
            futures.add(fileCompletableFuture);
        }

        return RestResponse.success(futures.stream().map(future-> {
            try
            {
                return future.get();
            }
            catch (InterruptedException | ExecutionException e)
            {
                throw new RuntimeException(e);
            }
        }).collect(Collectors.toList()));
    }

    public void batchUploadRestart(MultipartFile file, String ip)
    {
        SSHEntity sshEntity = getById(ip);
        if (Objects.isNull(sshEntity))
        {
            return;
        }
        List<String> fileNames = new ArrayList<>();
        List<String[]> remoteDirAndLocalFilePaths = new ArrayList<>();
        String fileName = saveToLocal(file,remoteDirAndLocalFilePaths);
        boolean restart = !StringUtil.isBlank(fileName);
        fileNames.add(fileName);
        List<SSHSessionInfo> sshSessionInfos = new ArrayList<>();
        sshSessionInfos.add(sshEntity.toInfo());
        batchUploadRestart(sshSessionInfos,remoteDirAndLocalFilePaths,restart,fileNames);
        FileUtil.deleteFile(new File(remoteDirAndLocalFilePaths.get(0)[1]));
    }

    private String saveToLocal(MultipartFile file,List<String[]> remoteDirAndLocalFilePaths)
    {
        String fileName = file.getOriginalFilename();
        String savePath = PropertiesHelper.basePath() + File.separator + fileName;
        FileUtil.deleteFile(new File(savePath));
        try(InputStream inputStream = file.getInputStream();
            FileOutputStream fileOut = new FileOutputStream(savePath))
        {
            IOUtils.copy(inputStream, fileOut);
            fileOut.flush();
        }
        catch (Exception ex)
        {
            log.error("save file to local fail", ex);
            throw new RuntimeException("save file to local fail");
        }
        if (fileName == null)
        {
            throw new RuntimeException("file name is null");
        }
        String jarName = null;
        if (fileName.endsWith("jar"))
        {
            jarName = fileName.replaceAll("\\.jar", "");
            remoteDirAndLocalFilePaths.add(new String[]{"/home/" + jarName + "/app", savePath});
        }
        else
        {
            remoteDirAndLocalFilePaths.add(new String[]{"/opt", savePath});
        }
        return jarName;
    }

    public RestResponse<Void> batchUploadRestart(SSHSessionInfo sshSessionInfo, List<String[]> remoteDirAndLocalFilePaths, boolean restart, List<String> fileNames)
    {
        RestResponse<Void> baseResponse = RestResponse.fail();
        try
        {
            baseResponse = onClientBuildFail();
            SSHJClient sshjClient = new SSHJClient(sshSessionInfo.getIp(), sshSessionInfo.getPort(), sshSessionInfo.getUsername(), sshSessionInfo.getPassword());
            baseResponse = onUploadFail();
            sshjClient.putFiles(remoteDirAndLocalFilePaths);
            if (restart)
            {
                baseResponse = onShellCmdFail();
                List<String> restartShellCmd = buildShellCmd(fileNames, sshSessionInfo.getRestartServerCommand());
                if (!restartShellCmd.isEmpty())
                {
                    sshjClient.shellCmd(restartShellCmd.toArray(new String[0]));
                }
            }
            baseResponse = onReleaseFail();
            sshjClient.release();
        }
        catch (Exception e)
        {
            log.error("upload fail",e);
            return baseResponse;
        }
        return RestResponse.success();
    }

    public RestResponse<Void> connect(SSHSessionInfo sshSessionInfo)
    {
        try
        {
            Runtime.getRuntime().exec("cmd /k start cmd.exe");

            robotHelper.delaySecond(1);
            robotHelper.inputEndEnter("ssh-keygen -R " + sshSessionInfo.getIp());
            robotHelper.delaySecond(3);
            robotHelper.inputEndEnter("ssh -p " + sshSessionInfo.getPort() + " " + sshSessionInfo.getUsername() +"@" + sshSessionInfo.getIp());
            robotHelper.delaySecond(1);
            robotHelper.inputEndEnter("yes");
            robotHelper.delaySecond(5);
            robotHelper.inputEndEnter(sshSessionInfo.getPassword());
        }
        catch (Exception e)
        {
            return RestResponse.fail("命令执行失败");
        }
        return RestResponse.success();
    }

    public RestResponse<String> getDownloadPath()
    {
        FileSystemView fsv = FileSystemView.getFileSystemView();
        // 默认为桌面路径
        File currentPath = fsv.getHomeDirectory();

        JFileChooser fileChooser = new JFileChooser();
        fileChooser.setCurrentDirectory(currentPath);
        fileChooser.setDialogTitle("请选择下载路径");
        fileChooser.setApproveButtonText("确定");
        fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
        int result = fileChooser.showOpenDialog(null);
        if (JFileChooser.APPROVE_OPTION == result)
        {
            String path = fileChooser.getSelectedFile().getPath();
            return RestResponse.success(path);
        }
        return RestResponse.fail("未选择下载路径");
    }

    public RestResponse<List<RemoteFileInfo>> getRemoteFileNames(SSHSessionInfo sshSessionInfo,String remoteDir)
    {
        try
        {
            SSHJClient sshjClient = SSHJClientManager.getSSHJClient(sshSessionInfo);
            List<RemoteFileInfo> remoteFileNames = sshjClient.getFileNames(remoteDir);
            return RestResponse.success(remoteFileNames);
        }
        catch (Exception e)
        {
            return RestResponse.fail("获取远程文件列表失败");
        }
    }

    public LastQueryInfo getLastQueryInfo()
    {
        return lastQueryInfo;
    }

    public List<FileInfo> listLocalFile(String path,int queryMode,String queryContent)
    {
        lastQueryInfo.setPath(path);
        lastQueryInfo.setQueryMode(queryMode);
        lastQueryInfo.setQueryContent(queryContent);
        switch (queryMode)
        {
            case 0:
                List<File> files = FileUtil.fuzzyFindFile(path, queryContent);
                return files.stream().map(FileInfo::new).collect(Collectors.toList());
            case 1:
                List<File> fullFindFiles = FileUtil.fullFindFile(path, queryContent);
                return fullFindFiles.stream().map(FileInfo::new).collect(Collectors.toList());
            case 2:
                List<File> prefixFindFiles = FileUtil.prefixFindFile(path, queryContent);
                return prefixFindFiles.stream().map(FileInfo::new).collect(Collectors.toList());
            case 3:
                List<File> suffixFindFiles = FileUtil.suffixFindFile(path, queryContent);
                return suffixFindFiles.stream().map(FileInfo::new).collect(Collectors.toList());
            case 4:
                List<File> regexFindFiles = FileUtil.regexFindFile(path, queryContent);
                return regexFindFiles.stream().map(FileInfo::new).collect(Collectors.toList());
        }
        return Collections.emptyList();
    }

    private RestResponse<Void> onClientBuildFail()
    {
        return RestResponse.fail("服务连接失败");
    }

    private RestResponse<Void> onUploadFail()
    {
        return RestResponse.fail("文件上传失败");
    }

    private RestResponse<Void> onReleaseFail()
    {
        return RestResponse.fail("服务释放失败");
    }

    private RestResponse<Void> onShellCmdFail()
    {
        return RestResponse.fail("命令执行失败");
    }
}
